

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 *
 * @author Adam Dickinson
 */
public class SMTPConnection implements Runnable {

    private final BufferedReader fromClient;
    private final DataOutputStream toClient;
    private static final String CRLF = "\r\n";
    private static final String sendServer = "mail.optusnet.com.au";

    public enum ListenState {

        CONNECTED, OPEN, MAIL, DATA
    }
    private Socket fClient;
    private boolean isConnected;
    private ListenState fState;
    private Message fEmail;
    private String fSender;
    private String fRecipient;
    private String fData;
    private String fsubject;

    public SMTPConnection(Socket aClient) throws Exception {
        isConnected = true;
        fClient = aClient;
        fsubject = ""; 
        fRecipient = "";
        fsubject = "";
		fData ="";
        fromClient = new BufferedReader(new InputStreamReader(
                fClient.getInputStream()));
        toClient = new DataOutputStream(fClient.getOutputStream());
        toClient.writeBytes("220 Assignment 4 Email server - SMTP" + CRLF);
        fState = ListenState.CONNECTED;
    }

    /**
     * Takes a command message and passes it into the appropriate command
     *
     * @param message
     */
    public void processCommand(String mess) {
        String lReturnMessage = "";
        String aMessage = mess.trim().toUpperCase();


        if (aMessage.startsWith("HELO") && fState == ListenState.CONNECTED) {
            lReturnMessage = processHelo(aMessage);
        }

        if (aMessage.startsWith("MAIL FROM:") && fState == ListenState.OPEN ) {
            lReturnMessage = processMail(aMessage);
        }

        if (aMessage.startsWith("RCPT TO:")) {
            lReturnMessage = processRcpt(aMessage);
        }

        if (aMessage.startsWith("DATA") || fState == ListenState.DATA) {
            if (aMessage.startsWith("SUBJECT:") && fState == ListenState.DATA) {

                fsubject = aMessage.substring(aMessage.indexOf(":") + 1, aMessage.length());
            } else {
                lReturnMessage = processData(aMessage);
            }
        }



        if (aMessage.equalsIgnoreCase(".") && fState == ListenState.DATA) {
            lReturnMessage = processDot();
        }

        if (aMessage.startsWith("QUIT")) {
            close();
        }

        if (lReturnMessage.isEmpty() && fState != ListenState.DATA) {
            lReturnMessage = "502 Unknown command !@";
        }

        if (!lReturnMessage.isEmpty()) {
            returnMessage(lReturnMessage);
        }
    }

    /**
     *
     * @param aMessage
     * @return
     */
    public String processHelo(String aMessage) {
        fState = ListenState.OPEN;

        // in the book is says 250 hello crepes.fr, pleased to meet you 
        return "250 Hello you";
    }

    public String processMail(String aMessage) {

        fState = ListenState.MAIL;

        if (aMessage.indexOf("<") == -1 || aMessage.indexOf(">") == -1) {
            return "502 Unknown Command !";
        } else {

            fSender = aMessage.substring(aMessage.indexOf("<") + 1, aMessage.indexOf(">")).toLowerCase();

            return "250 sender: " + fSender;
        }
    }

    public String processRcpt(String aMessage) {
        if (fState == ListenState.CONNECTED || fState == ListenState.OPEN) {
            return "503 Commands out of order";
        }

        if (aMessage.indexOf("<") == -1 || aMessage.indexOf(">") == -1) {
            return "502 Unknown Command !";
        } else {

            fRecipient += aMessage.substring(aMessage.indexOf("<") + 1, aMessage.indexOf(">")).toLowerCase();
            fRecipient += CRLF;
            return "250 recipient: " + fRecipient;
        }
    }

    public String processData(String aMessage) {
        if (fState == ListenState.CONNECTED || fState == ListenState.OPEN) {
            return "503 Commands out of order";
        }
        // terry added trim 
        if (aMessage.trim().equalsIgnoreCase("DATA") && fState != ListenState.DATA) {
            fState = ListenState.DATA;
            return "354 End data with <CR><LF>.<CR><LF>";
        }
       

        fData += aMessage + CRLF;




        return "";
    }

    public String processDot() {
        if (fRecipient == "") {
            return "554 No Valid Recipients";
        }

        String lRecipients[] = fRecipient.split(CRLF);

        for (String lRecipient : lRecipients) {

            Message msg = new Message(fSender, lRecipient, fsubject, fData);

            if (fRecipient.contains("@")) {
                sendMail(msg);
            } else {
                saveMessage(msg);
            }
        }


        // Clear the message data
        fSender = "";
        fRecipient = "";
        fsubject = "";
        fData = "";

        fState = ListenState.OPEN;
        return "250 Message Sent";
    }

    public void returnMessage(String aMessage) {
        try {
            toClient.writeBytes(aMessage + CRLF);
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

    public void saveMessage(Message message) {
        try {
            UserMail user = UserMail.open(message.getTo());
            user.add(message);
            user.save();
        } catch (Exception ex) {
            System.err.println(ex.getMessage());
        }

    }

    public void sendMail(Message aSend) {
        try {
            Envelope lSendEnv = new Envelope(aSend, sendServer);
            SMTPOutgoingConnection outgoing = new SMTPOutgoingConnection(lSendEnv);
            outgoing.send(lSendEnv);
            outgoing.close();
        } catch (Exception ex) {
            // System.out.println(ex.getMessage());            
        }
    }

    // Close the connection. Try to send QUIT-command and then close the socket. 
    public void close() {
        isConnected = false;
        try {
            returnMessage("221 Bye");
            fClient.close();
        } catch (IOException e) {
            System.out.println("Unable to close connection: " + e.getMessage());
            isConnected = true;
        }
    }

    @Override
    public void run() {
        String command;
        try {
            while (true) {
                command = fromClient.readLine();
                processCommand(command);
            }
        } catch (Exception ex) {
            System.out.println(ex.getMessage());

        }
    }

    @Override
    public void finalize() throws Throwable {
        if (isConnected) {
            close();
        }
        super.finalize();
    }
}
